/*
 * Copyright (c) 2000-2003 Yale University. All rights reserved. THIS SOFTWARE IS PROVIDED "AS IS," AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ARE EXPRESSLY DISCLAIMED.
 * IN NO EVENT SHALL YALE UNIVERSITY OR ITS EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED, THE COSTS OF PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED IN ADVANCE OF THE POSSIBILITY OF SUCH DAMAGE.
 * Redistribution and use of this software in source or binary forms, with or without modification, are permitted, provided that the
 * following conditions are met: 1. Any redistribution must include the above copyright notice and disclaimer and this list of conditions in
 * any related documentation and, if feasible, in the redistributed software. 2. Any redistribution must include the acknowledgment, "This
 * product includes software developed by Yale University," in any related documentation and, if feasible, in the redistributed software. 3.
 * The names "Yale" and "Yale University" must not be used to endorse or promote products derived from this software.
 */

package com.phevos.web.cas.client.taglib;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspTagException;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.TagSupport;
import javax.xml.parsers.ParserConfigurationException;

import org.xml.sax.SAXException;

import com.phevos.web.cas.client.ProxyTicketValidator;
import com.phevos.web.cas.client.Util;

/**
 * <p>
 * Authentication tag for use with the Yale Central Authentication Service.
 * </p>
 * <p>
 * Typical usage involves placing the tag at the top of the page. The tag checks to determine if the attribute referenced by id/scope
 * exists; if it does, the tag has no runtime effect. If the attribute does not exist, however, a CAS authentication is necessary: if no
 * ticket is present, we redirect to CAS, and if a ticket is present, we validate it. Upon successful CAS authentication (either by a
 * pre-existing attribute or through CAS directly), we store the NetID in the attribute referenced by id/scope.
 * </p>
 * 
 * @author Shawn Bayern
 * @author Drew Mazurek
 */
public class AuthTag extends TagSupport
{

    // *********************************************************************
    // Internal state

    /**
     * 
     */
    private static final long serialVersionUID = 450489421895038550L;

    private String var; // tag attribute

    private int scope; // tag attribute

    private String casLogin, casValidate, service; // from children

    private List acceptedProxies; // from children

    private HttpServletRequest request;

    private HttpServletResponse response;

    // *********************************************************************
    // Tag logic
    /**
     * doStartTag
     * 
     * @return int
     * @throws JspException
     */
    public int doStartTag() throws JspException
    {
        // retrieve and save the request and response objects
        request = (HttpServletRequest)pageContext.getRequest();
        response = (HttpServletResponse)pageContext.getResponse();

        // reset invocation-specific state
        casLogin = null;
        casValidate = null;
        try
        {
            service = Util.getService(request);
            // Util.getService(
            // request,
            // (String) pageContext.getServletContext().getInitParameter(
            // "edu.yale.its.tp.cas.serverName"));
        }
        catch (ServletException ex)
        {
            throw new JspException(ex);
        }
        acceptedProxies = new ArrayList();
        return EVAL_BODY_INCLUDE;
    }

    /**
     * doEndTag
     * 
     * @return int
     * @throws JspTagException
     */
    public int doEndTag() throws JspTagException
    {
        try
        {
            // if our attribute's already present, don't do anything
            if (pageContext.getAttribute(var, scope) != null)
                return EVAL_PAGE;

            // otherwise, we need to authenticate via CAS
            String ticket = request.getParameter("ticket");

            // no ticket? redirect...
            if (ticket == null || ticket.equals(""))
            {
                if (casLogin == null)
                    throw new JspTagException("for pages that expect to be called without 'ticket' parameter, "
                            + "cas:auth must have a cas:loginUrl subtag");
                response.sendRedirect(casLogin + "?service=" + service);
                return SKIP_PAGE;
            }

            // Yay, ticket! Validate it.
            String netid = getAuthenticatedNetid(ticket);
            if (netid == null)
                throw new JspTagException("Unexpected CAS authentication error");

            // Store the authenticate user in the id/scope attribute
            pageContext.setAttribute(var, netid, scope);

            return EVAL_PAGE;

        }
        catch (IOException ex)
        {
            throw new JspTagException(ex.getMessage());
        }
        catch (SAXException ex)
        {
            throw new JspTagException(ex.getMessage());
        }
        catch (ParserConfigurationException ex)
        {
            throw new JspTagException(ex.getMessage());
        }
    }

    // *********************************************************************
    // Attribute accessors
    /**
     * setVar
     * 
     * @param var String
     */
    public void setVar(String var)
    {
        this.var = var;
    }

    /**
     * setScope
     * 
     * @param scope String
     */
    public void setScope(String scope)
    {
        if (scope.equals("page"))
            this.scope = PageContext.PAGE_SCOPE;
        else if (scope.equals("request"))
            this.scope = PageContext.REQUEST_SCOPE;
        else if (scope.equals("session"))
            this.scope = PageContext.SESSION_SCOPE;
        else if (scope.equals("application"))
            this.scope = PageContext.APPLICATION_SCOPE;
        else
            throw new IllegalArgumentException("invalid scope");
    }

    // *********************************************************************
    // Accessors for child tags

    public void setCasLogin(String url)
    {
        casLogin = url;
    }

    public void setCasValidate(String url)
    {
        casValidate = url;
    }

    /**
     * addAuthorizedProxy
     * 
     * @param proxyId String
     */
    public void addAuthorizedProxy(String proxyId)
    {
        acceptedProxies.add(proxyId);
    }

    public void setService(String service)
    {
        this.service = service;
    }

    // *********************************************************************
    // Constructor and lifecycle management
    /**
     * AuthTag()
     */
    public AuthTag()
    {
        super();
        init();
    }

    /**
     * Releases any resources we may have (or inherit)
     */
    public void release()
    {
        super.release();
        init();
    }

    // clears any internal state we might have
    private void init()
    {
        var = null;
        scope = PageContext.PAGE_SCOPE;
        casLogin = null;
        casValidate = null;
        acceptedProxies = null;
    }

    // *********************************************************************
    // Utility methods

    private String getAuthenticatedNetid(String ticket) throws ParserConfigurationException, SAXException, IOException, JspTagException
    {
        ProxyTicketValidator pv = new ProxyTicketValidator();
        pv.setCasValidateUrl(casValidate);
        pv.setServiceTicket(ticket);
        pv.setService(service);
        pv.validate();
        if (!pv.isAuthenticationSuccesful())
            throw new JspTagException("CAS authentication error: " + pv.getErrorCode());
        if (pv.getProxyList().size() != 0)
        {
            // ticket was proxied
            if (acceptedProxies.size() == 0)
                throw new JspTagException("this page does not accept proxied tickets");
            else if (!acceptedProxies.contains(pv.getProxyList().get(0)))
                throw new JspTagException("unauthorized top-level proxy: '" + pv.getProxyList().get(0) + "'");
        }
        return pv.getUser();
    }
}
